
#include "../main.h"

#include <ctype.h>

#define CLASS Server


//////////////////////////////////////////////////////////////////////////////
//
static void server_finalize(JSContext *cx, JSObject *obj){
}






#define BUFFER_SIZE 10000


void server_listener(void *param) {
	trace1("server_listener");
	
	js_thread_t *x = (js_thread_t *)param;
	
	
	
	//jsrefcount rc;
	//rc = JS_SuspendRequest(y->cx);
	
	
	//PRFileDesc *sock = (PRFileDesc *)param;
	
	PRFileDesc *sock = x->socket;
	PRNetAddr *addr;
	int len, l;
	PRFileDesc *client;
	int nmax = BUFFER_SIZE + 1;
	char *buf;
	char *pmax;
	char *ptr = buf;
	char *end;
	char *b;
	char *method, *uri;
	char *header_name, *header_value;
	int content_length;
	char keep_alive;
	char *data;
	
	
	
	
	//trace2("waiting for connection");
	
	//buf = (char *)calloc(nmax + 1, 1);
	buf = (char *)malloc((nmax + 1) * sizeof(char ));

acceptread:	
	
	end = buf;
	pmax = buf + nmax;

	l = PR_AcceptRead(sock, &client, &addr, buf, pmax - end, PR_INTERVAL_NO_TIMEOUT);
	//l = PR_AcceptRead(sock, &client, &addr, buf, pmax - end,  PR_INTERVAL_MIN);
	end += l;

	trace8("got connection");
	trace8("initial read l=%d", l);
	
	//l = PR_AcceptRead(sock, &client, &addr, buf, BUFFER_SIZE, PR_INTERVAL_NO_WAIT);
	//l = PR_AcceptRead(sock, &client, &addr, buf, BUFFER_SIZE, PR_INTERVAL_MIN);	
		
	PR_CreateThread(PR_USER_THREAD, server_listener, (void *)x, PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_UNJOINABLE_THREAD, 0);

	goto process_request;


keep_alive:
	trace9("keep-alive. reading next request...");
	end = buf;
	//PR_Write(client, "\r\n", 2);
	//do {
		l = PR_Read(client, end, pmax - end);
		trace9("l=%d", l);
	//} while (l == 0);
	end += l;

process_request:

	keep_alive = 0;
	content_length = -1;
	ptr = buf;
	method = "";
	uri = "";


	if (l == -1) {
		trace2("ERROR: %d, OS ERROR: %d", PR_GetError(), PR_GetOSError());
		return;
	}
	

	// continues listening in another thread
	

	char bNeedMore;


		

	while (l) {
		bNeedMore = 1;
		
		if (end >= pmax) {
			fatal("Buffer overflow");
		}

#if VERBOSE_LEVEL > 8
		
		{
			int i;
			char out[10000] = "", o[100];
			for (b = ptr; b < end; ++b) {
				sprintf(o, "%02X ", *b);
				strcat(out, o);
			}	
			trace8("got bytes: %s", out);
		}
		//trace2("got bytes: %02X %02X %02X %02X", ptr[0], ptr[1], ptr[2], ptr[3]);
#endif
		
		
		

		ptr = buf;
		for ( ; ptr < end; ++ptr) {
			switch (*ptr) {
				case '\r': case '\n': continue;
			}
			break;
		}

		
		for (; ptr < end - 2; ++ptr) {
			if (*ptr == '\r') ++ptr;
			if (*ptr != '\n') continue;
			++ptr;
			if (*ptr == '\r') ++ptr;
			if (*ptr != '\n') continue;
			bNeedMore = 0;
			break;
		}
		trace9("bNeexMore = %d", bNeedMore);
		
		//--ptr;
		ptr = end - 1;
		
		if (!bNeedMore) break;
		
		trace8("need more! reading next chunk");
		
		//PR_Sleep(100);
		trace9("buf=%p, end=%p, pmax-end=%d", buf, end, pmax-end);
		
		//l = PR_Read(sock, buf, 100);
		//l = PR_Read(client, end, pmax - end);
		l = PR_Read(client, end, pmax - end);
		if (l == -1) {
			fatal("PR_Read");
		}
			
		
		//trace2("xxx");
		
		end += l;
		
		trace9("next chunk length = %d", l);

		if (l)
			ptr -= 2;
	}

	trace9("parsing request");
	
	ptr = buf;

	// skiping empty lines
	for ( ; ptr < end; ++ptr) {
		switch (*ptr) {
			case ' ': case '\r': case '\n': continue;
		}
		break;
	}


	// method
	for (b = ptr; ptr < end; ++ptr) {
		switch (*ptr) {
			default: continue;
			case ' ': case '\r': case '\n': *(ptr++) = 0; break;
		}
		method = b;
		break;
	}

	// uri
	for(; ptr < end; ++ptr) { switch (*ptr) { case ' ': case '\r': case '\n': continue; } break; }
	for (b = ptr; ptr < end; ++ptr) {
		switch (*ptr) { default: continue; case ' ': case '\r': case '\n': *(ptr++) = 0; }
		uri = b;
		break;
	}

	// skip till end of line
	for(; (ptr < end) && (*(ptr++) != '\n'); );

	trace9("method=[%s], uri=[%s]", method, uri);

	// reading headers
	while (ptr < end) {
		if (*ptr == '\r') ++ptr;
		if (*ptr == '\n') {
			++ptr;
			break;
		}
		
		header_name = NULL;
		header_value = NULL;

		// header name
		for (b = ptr; ptr < end; ++ptr) {
			switch (*ptr) { default: continue; case ':': *(ptr++) = 0; }
			header_name = b;
			
			break;
		}
	
		// skip spaces
		for(; (ptr < end) && (*ptr == ' '); ++ptr);
	
		// header value
		for (b = ptr; ptr < end; ++ptr) {
			switch (*ptr) {
				default: continue;
				case '\r': *ptr = 0; continue;
				case '\n': *(ptr++) = 0; break;
			}
			{
				char *e;
				for (e = ptr - 1; (*e == ' ') && (e >= b); --e)
					*e = 0;
			}
			header_value = b;
			
			break;
		}
		
		if (header_name && header_value) {
			trace6("HEADER [%-15s]: [%s]", header_name, header_value);
			if (strcmp(header_name, "Content-Length") == 0) {
				content_length = atoi(header_value);
				trace6("got content-length: %d bytes", content_length);
			} else if (strcmp(header_name, "Connection") == 0) {
				if (strcmp(header_value, "Keep-Alive") == 0) {
					trace9("turn on keep-alive for next request");
					keep_alive = 1;
				}
			}
		}
	}
	trace6("headers finished");		
	
	data = NULL;
	
	if (strcmp(method, "POST") == 0) {
		data = ptr;
		if (ptr == end) {
			trace9("additional reading of post data");
			l = content_length > 0 ? content_length : pmax - ptr;
			trace9("waiting max %d bytes", l);
			l = PR_Read(client, ptr, l);
			//l = PR_RecvFrom(client, ptr, pmax - ptr, 0, addr, PR_INTERVAL_NO_TIMEOUT);  // PR_INTERVAL_NO_WAIT
			trace9("l=%d", l);

			//char out[10000] = "", o[1000] = "";
			
			if (l == -1) {
				trace2("ERROR: %d, OS ERROR: %d", PR_GetError(), PR_GetOSError());
				goto end;
			}
			
			
			/*
			sprintf(out, "got %d bytes", l);
			if (l > 0)
				strcat(out, ": ");
			int n = 0;
			for (; (n < l) && (n < 4); ++n) {
				sprintf(o, "%02X ", ptr[n]);
				strcat(out, o);
			}
			if (n < l)
				strcat(o, " ...");
			trace2("%s", out);
			*/
			
			end += l;
			ptr = end;
		}
		*end = 0;

		trace9("\n---------- BEGIN POST DATA ---------\n%s\n---------- END POST DATA ---------", data);
		


		
		/*
		JSContext *cx = JS_NewContext(rt, 8096);
		JS_SetOptions(x->cx, JSOPTION_DONT_REPORT_UNCAUGHT | JSOPTION_VAROBJFIX | JSOPTION_JIT);
		JS_SetScriptStackQuota(x->cx, 16*1024*1024);
		*/
		
		

	}
	
	

end:	

//	JS_ResumeRequest(y->cx, rc);
	
	//trace2("END");
	if (!keep_alive)
		PR_Shutdown(client, PR_SHUTDOWN_RCV);
	


	static PRLock *lock = NULL;
	if (!lock) lock = PR_NewLock();
	
	char *json = NULL;
	
	{
		char *out = (char *)malloc(100000);
		*out = 0;
		
		char *o = (char *)malloc(1000);
		
		//char body[10000] = "";
		char *body = NULL;
		body = (char *)malloc(100);
		*body = 0;

		PR_Lock(lock);

		if (data) {
			//js_thread_set_lock(y, 1);
			trace6("calling javascript function");
			js_thread_t *y = get_thread_slot(NULL);
			init_thread_context(y);
			JSContext *cx = y->cx;

			//init_thread_context(y);
			//JSContext *cx = create_context(NULL);
			JS_BeginRequest(cx);
				
				jsval args[1];
				jsval r;
				//JS_AddNamedValueRoot(y->cx, &r, "r");
				//JS_AddNamedValueRoot(y->cx, &args[0], "args");
				//if (!json_decode(y->cx, "{\"method\":\"get_random\",\"params\":[20,30]}", args))
				if (!json_decode(cx, data, &args[0]))
					trace2("json_decode error!");
				else  
					if (JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "onjsonrequest", 1, args, &r)) {
						json = json_encode(cx, r);
						if (!json)
							trace2("json_encode returned null");
						else {
							body = json;
							trace9("JSON:\n%s\n", json);
						}
					}
				//JS_RemoveValueRoot(y->cx, &r);
				//JS_RemoveValueRoot(y->cx, &args[0]);
				
			
			JS_MaybeGC(cx);
			JS_EndRequest(cx);
			//JS_ClearContextThread(y->cx);	
			
			
			y->closed = 1;
			
			//JS_GC(cx);
			//JS_EndRequest(cx);
			//JS_ClearContextThread(cx);
			//JS_DestroyContext(cx);
			//js_thread_set_lock(y, 0);
			
		}
		PR_Unlock(lock);
		trace6("forming reply");
		trace6("body:%s", body);
		trace6("out:%s", out);

		
		if (x->document_root) {
			for (b = uri; *b; ++b)
				if (*b == '?') break;
			l = b - uri;
			char *pathname = (char *)malloc(l + 1);
			memcpy(pathname, uri, l);
			pathname[l] = 0;		
			char *filename = (char *)malloc(strlen(x->document_root) + l + 1);
			strcpy(filename, x->document_root);
			strcat(filename, pathname);
			trace9("filename='%s'", filename);

			//trace2("Content-Type: %s", get_file_content_type(filename));
			
			char *content_type = get_file_content_type(filename);
			
			PRFileDesc *file = PR_Open(filename, PR_RDONLY, 0);
			if (file) {
				PRFileInfo info;
				PR_GetOpenFileInfo(file, &info);
				switch (info.type) {
					case PR_FILE_FILE:
						trace9("Sending file");
						char *headers = (char *)malloc(1000);
						char *name = basename(filename);

						if (content_type)
							len = sprintf(headers, "HTTP/1.0 200 OK\r\nContent-Length: %d\r\nContent-Type: %s\r\n\r\n", info.size, content_type);
						else
							len = sprintf(headers, "HTTP/1.0 200 OK\r\nContent-Length: %d\r\nContent-Disposition: attachment; filename=\"%s\"\r\n\r\n", info.size, name);
						
						free(name);
						PR_TransmitFile(
							client,
							file,
							headers,
							len,
							keep_alive ? PR_TRANSMITFILE_KEEP_OPEN : PR_TRANSMITFILE_CLOSE_SOCKET,
							PR_INTERVAL_NO_TIMEOUT
						);
							free(headers);
						PR_Close(file);
						if (keep_alive) {
							trace9("keeping alive after file was sent");
							goto keep_alive;
						}
						trace9("returning after file was sent");
						return;
					
					default:
						PR_Close(file);
				}
			}
		}
		
		
		

		if (strcmp(uri, "/favicon.ico") == 0) {
			strcat(out, "HTTP/1.0 404 Not Found\r\n");
			//strcpy(body, "not found");
		} else {
			strcat(out, "HTTP/1.0 200 OK\r\n");
			strcat(out, "Content-Type: text/plain; charset=UTF-8\r\n");
			//strcpy(body, "privet");
		}
		if (!body)
			strcpy(body, "Unknown error");
		
		content_length = strlen(body);
		
		trace9("content_length: %d", content_length);
		
		if (!keep_alive)
			strcat(out, "Connection: close\r\n");
		else {
			strcat(out, "Connection: keep-alive\r\n");
		}
		*o = 0;
		sprintf(o, "Content-Length: %d\r\n", content_length);
		strcat(out, o);
		
		
		strcat(out, "\r\n");

		//strcat(out, body);
		PR_Write(client, out, strlen(out));
		PR_Write(client, body, content_length);

		
		//strcpy(out, body);
		//PR_Write(client, out, content_length);	
	}
	
	if (!keep_alive)
		PR_Shutdown(client, PR_SHUTDOWN_SEND);

	trace9("keep_alive = %d", keep_alive);


	if (keep_alive) {
		//PR_Sleep(1000);
		goto keep_alive;
	}

	PR_Close(client);
	
	trace6("closed");

	
	
	
	
//	free(buf);
//	if (json) free(json);

//	trace2("sleeping 500");
//	PR_Sleep(500);
//	trace2("zzz");


	
	//y->closed = 1;


//	goto acceptread;		
	
//	PR_CreateThread(PR_USER_THREAD, server_listener, (void *)x, PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_UNJOINABLE_THREAD, 0);
	
}





void server_thread_proc(void *param) {
	js_thread_t *x = (js_thread_t *)param;
	trace1("server_thread_proc()");
	trace2("call");
	int len, l;
	int port = 51234;
	char *b, *buf = (char *)malloc(DEFAULT_BUF_SIZE);

	trace2("creating new context...");
	JSContext *cx = JS_NewContext(rt, 8096);
	if (!cx)
		fatal("JS_NewContext()");	
	JS_SetOptions(cx, JSOPTION_DONT_REPORT_UNCAUGHT | JSOPTION_VAROBJFIX | JSOPTION_JIT);
	JS_SetScriptStackQuota(cx, 16*1024*1024);
	
	JS_BeginRequest(cx);
		jsval v;
		JS_GetProperty(cx, x->thisObj, "port", &v);
		port = JSVAL_TO_INT(v);

		JS_GetProperty(cx, x->thisObj, "document_root", &v);
		x->document_root = vtoa(cx, v);
	JS_EndRequest(cx);

/*
	JS_BeginRequest(cx);
	JS_EndRequest(cx);
*/	
	
	trace2("port: %d", port);
	
	PRFileDesc *client;
	PRNetAddr any_address, *client_address;
	PRFileDesc *sock = PR_OpenTCPSocket(PR_AF_INET);
	PRSocketOptionData opt;
	opt.option = PR_SockOpt_Reuseaddr;
	opt.value.reuse_addr = PR_TRUE;
	PR_SetSocketOption(sock, &opt);
	PR_InitializeNetAddr(PR_IpAddrAny, port, &any_address);
	PR_Bind(sock, &any_address);

	trace2("listening");
	PR_Listen(sock, 10);
	
	x->socket = sock;
	server_listener(x);
	return;


	js_thread_t *y;
	jsrefcount rc;
	
	while(1){
		trace2("waiting for a new connection");
		
		
		y = get_thread_slot(cx);
		
		rc = JS_SuspendRequest(cx);

		len = PR_AcceptRead(sock, &y->client, &client_address, buf, DEFAULT_BUF_SIZE, PR_INTERVAL_NO_TIMEOUT);
		trace2("got connection!");
		b = buf + len;
		
		
		
				

		trace2("initial read: %d bytes", len);
		while (1) {
			*b = 0;
			if ((len > 3) && (strcmp(b - 3, "\n\r\n") == 0))
				break;
			l = PR_Read(y->client, b, DEFAULT_BUF_SIZE - len);
			trace2("next input chunk: %d bytes", l);
			len += l;
			b += l;
		}
		
		//b = buf + len;
		//len = 
		//trace2("second read: %d bytes", len);
		
		
		JS_ResumeRequest(cx, rc);
		
		//b = buf + len;
		if (len > 0)
			do *(b--) = 0; while ((*b == '\n') || (*b == '\r'));
		
		trace2("Creating javascript thread");	
		
		//JS_SetGlobalObject(cx, x->thisObj);
		y->func = x->func;
		y->thisObj = x->thisObj;
		y->global = x->thisObj;
		JS_BeginRequest(cx);
			y->argc = 1;
			y->argv[0] = atov(cx, buf);
		JS_EndRequest(cx);

		js_thread_proc(y);
		
		/*
		PR_CreateThread(
			PR_USER_THREAD,
			js_thread_proc,
			(void *)y,
			PR_PRIORITY_NORMAL,
			PR_LOCAL_THREAD,
			PR_UNJOINABLE_THREAD,
			0
		);
		*/
		
			
		//PR_Sleep(10);
	}
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool
server_run(JSContext *cx, uintN argc, jsval *vp) {
//server_listen(JSContext *cx, uintN argc, jsval *vp) {
	//uint32 port;
	//jsval func;
	trace1("server::run()");


	js_thread_t *x = get_thread_slot(cx);

	//JS_SetGlobalObject(x->cx, JS_THIS_OBJECT(cx, vp));

	x->global = JS_THIS_OBJECT(cx, vp);
/*	

	jsval v;
	if(!JS_GetProperty(cx, JS_THIS_OBJECT(cx, vp), "onrequest", &v)) {
		trace2("onrequest is not defined");
		return JS_FALSE;
	}

		
	if (!JS_ConvertValue(cx, v, JSTYPE_FUNCTION, &x->func)) {
		trace2("server::run(): no onrequest listener");
		return JS_FALSE;
	}
*/
	trace2("getting thread slot...");

	x->thisObj = JS_THIS_OBJECT(cx, vp);

	trace2("creating thread...");
	
	server_thread_proc(x);
	/*


	PR_CreateThread(
		PR_USER_THREAD,
		server_thread_proc,
		(void *)x,
		PR_PRIORITY_NORMAL,
		PR_LOCAL_THREAD,
		PR_UNJOINABLE_THREAD,
		0
	);
	*/
	
	JS_SET_RVAL(cx, vp, JSVAL_VOID);
	return JS_TRUE;
}



//////////////////////////////////////////////////////////////////////////////
//
static JSBool
server_read(JSContext *cx, uintN argc, jsval *vp) {
	trace1("read");
	uint32 max_bytes = DEFAULT_BUF_SIZE, len;
	char *b, *buf;
	if(!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "/u", &max_bytes)) return JS_FALSE;

	if ( ! (buf = (char *)malloc(max_bytes + 1) ) )
		JS_ReportOutOfMemory(cx);

	js_thread_t *x = (js_thread_t *)JS_GetContextPrivate(cx);
	
	jsrefcount rc = JS_SuspendRequest(cx);
	if(x && x->client)
		len = PR_Recv(x->client, buf, max_bytes, 0, PR_INTERVAL_NO_TIMEOUT);
	else {
		fgets(buf, max_bytes, stdin);
		len = strlen(buf);
	}
	JS_ResumeRequest(cx, rc);
	if(len<=0) return JS_FALSE;
	for ( b=buf+(strlen(buf)-1); (*b=='\n')||(*b=='\r'); *(b--)=0 );
	JS_SET_RVAL(cx, vp, atov(cx, buf));
	return JS_TRUE;
}	


//////////////////////////////////////////////////////////////////////////////
//
static JSBool
server_write(JSContext *cx, uintN argc, jsval *vp) {	
	trace1("Server::write()");
	trace2("call");
	char *text;	
	JS_BeginRequest(cx);	
		JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "s", &text);
	JS_EndRequest(cx);
	
	js_thread_t *x = (js_thread_t *)JS_GetContextPrivate(cx);
	//jsrefcount rc = JS_SuspendRequest(cx);
	if(x && x->client){
		int len = PR_Write(x->client, text, strlen(text));
		//JS_ResumeRequest(cx, rc);
		return len < 0 ? JS_FALSE : JS_TRUE;
	}
	printf("%s", text);
	//JS_ResumeRequest(cx, rc);
	JS_SET_RVAL(cx, vp, JSVAL_VOID);
	return JS_TRUE;
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool
server_open(JSContext *cx, uintN argc, jsval *vp) {	
	int port;	
	if (!JS_SetProperty(cx, JS_THIS_OBJECT(cx, vp), "port", JS_ARGV(cx, vp)))
		fatal("server_open(): cannot set port property of this object");		
	JS_SET_RVAL(cx, vp, JSVAL_VOID);
	return JS_TRUE;
}






static JSClass __class = {
    "Server", JSCLASS_GLOBAL_FLAGS,
    JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,

#if JS_VERSION > 180
    JS_StrictPropertyStub,
#else
    JS_PropertyStub,
#endif
    
    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, server_finalize,
    JSCLASS_NO_OPTIONAL_MEMBERS
};


static JSFunctionSpec methods[] = {
	JS_FN("run",    server_run,   0, 0),
	JS_FN("read",   server_read,  1, 0),
	JS_FN("write",  server_write, 1, 0),
	JS_FN("open",   server_open,  1, 0),
	JS_FS_END
};


//////////////////////////////////////////////////////////////////////////////
//
//static JSBool
//server_construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
//}

static JSBool construct(JSContext *cx, uintN argc, jsval *vp) {
	JSObject *obj = JS_NewObject(cx, &__class, NULL, NULL);
	JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
	return JS_TRUE;
}


//////////////////////////////////////////////////////////////////////////////
//
void class_server_init(JSContext *cx){
	JSObject *global = JS_GetGlobalObject(cx);
	JSObject *Server = JS_InitClass(cx, global, NULL, &__class,
		construct, 0,
		NULL, methods,
		NULL, NULL
	);
		
//		&__class, server_construct, 2, NULL, methods, NULL, NULL);


	//JSObject *obj = JS_DefineObject(cx, *global, "server", &__class, *global, JSPROP_ENUMERATE);
	//JS_DefineFunctions(cx, obj, methods);	
	//JS_SetGlobalObject(cx, obj);
	//*global = obj;
	
	
}




